今天我們要用做加密檔案的工具,以 AES 加密方式加密檔案。
主要是 AES 加密算是一個比較廣泛的技術,所以用 AES 加密基本上妥妥的,沒問題
那 AES 加密有幾個基本優點
安全性高
:經過多年的密碼學分析,至今沒有發現有效的攻擊方法效率佳
:在軟體和硬體實現上都有很好的性能標準化
:被 NIST(美國國家標準技術研究所)採用為官方標準廣泛支援
:幾乎所有的加密庫都支援 AES補充:AES 的全名為 Advanced Encryption Standard
先起個專案,老樣子
cargo new file_encryptor
cd file_encryptor
[dependencies]
aes = "0.8"
cbc = "0.1"
sha2 = "0.10"
pbkdf2 = "0.12"
rand = "0.8"
clap = { version = "4.0", features = ["derive"] }
hex = "0.4"
anyhow = "1.0"
這時候我們建立 src/crypto.rs
製作我們的核心加密組件
// src/crypto.rs
use aes::Aes256;
use cbc::{Decryptor, Encryptor};
use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use pbkdf2::pbkdf2_hmac;
use rand::{RngCore, rngs::OsRng};
use sha2::Sha256;
use anyhow::{Result, Context};
type Aes256CbcEnc = Encryptor<Aes256>;
type Aes256CbcDec = Decryptor<Aes256>;
const KEY_LENGTH: usize = 32; // AES-256
const IV_LENGTH: usize = 16; // AES block size
const SALT_LENGTH: usize = 32;
const PBKDF2_ITERATIONS: u32 = 100_000;
pub struct FileEncryption {
key: [u8; KEY_LENGTH],
}
impl FileEncryption {
/// 從密碼派生金鑰
pub fn from_password(password: &str, salt: &[u8]) -> Result<Self> {
if salt.len() != SALT_LENGTH {
anyhow::bail!("Salt must be {} bytes long", SALT_LENGTH);
}
let mut key = [0u8; KEY_LENGTH];
pbkdf2_hmac::<Sha256>(
password.as_bytes(),
salt,
PBKDF2_ITERATIONS,
&mut key,
);
Ok(Self { key })
}
/// 生成隨機鹽值
pub fn generate_salt() -> [u8; SALT_LENGTH] {
let mut salt = [0u8; SALT_LENGTH];
OsRng.fill_bytes(&mut salt);
salt
}
/// 加密資料
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
// 生成隨機 IV
let mut iv = [0u8; IV_LENGTH];
OsRng.fill_bytes(&mut iv);
// 為 PKCS7 padding 準備緩衝區
let mut buffer = data.to_vec();
let padding_len = 16 - (data.len() % 16);
buffer.extend(vec![padding_len as u8; padding_len]);
// 執行加密
let cipher = Aes256CbcEnc::new(&self.key.into(), &iv.into());
let encrypted = cipher.encrypt_padded_vec_mut::<cbc::cipher::block_padding::NoPadding>(&buffer)
.context("Encryption failed")?;
// 將 IV 和加密資料組合
let mut result = Vec::with_capacity(IV_LENGTH + encrypted.len());
result.extend_from_slice(&iv);
result.extend_from_slice(&encrypted);
Ok(result)
}
/// 解密資料
pub fn decrypt(&self, encrypted_data: &[u8]) -> Result<Vec<u8>> {
if encrypted_data.len() < IV_LENGTH {
anyhow::bail!("Encrypted data too short");
}
// 分離 IV 和加密資料
let (iv, encrypted) = encrypted_data.split_at(IV_LENGTH);
// 執行解密
let cipher = Aes256CbcDec::new(self.key.as_slice().into(), iv.into());
let mut decrypted = encrypted.to_vec();
let decrypted_data = cipher.decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut decrypted)
.context("Decryption failed")?;
// 移除 PKCS7 padding
let padding_len = *decrypted_data.last().unwrap() as usize;
if padding_len > 16 || padding_len == 0 {
anyhow::bail!("Invalid padding");
}
let data_len = decrypted_data.len() - padding_len;
Ok(decrypted_data[..data_len].to_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encryption_decryption() {
let password = "test_password_123";
let salt = FileEncryption::generate_salt();
let encryption = FileEncryption::from_password(password, &salt).unwrap();
let original_data = b"Hello, this is a test message for encryption!";
let encrypted = encryption.encrypt(original_data).unwrap();
let decrypted = encryption.decrypt(&encrypted).unwrap();
assert_eq!(original_data, decrypted.as_slice());
}
#[test]
fn test_different_passwords_different_keys() {
let salt = FileEncryption::generate_salt();
let enc1 = FileEncryption::from_password("password1", &salt).unwrap();
let enc2 = FileEncryption::from_password("password2", &salt).unwrap();
assert_ne!(enc1.key, enc2.key);
}
}
// src/file_ops.rs
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use anyhow::{Result, Context};
use crate::crypto::{FileEncryption, SALT_LENGTH};
const MAGIC_HEADER: &[u8] = b"RUSTENC1"; // 8 bytes magic header
const HEADER_SIZE: usize = MAGIC_HEADER.len() + SALT_LENGTH; // 8 + 32 = 40 bytes
pub fn encrypt_file<P: AsRef<Path>>(
input_path: P,
output_path: P,
password: &str,
) -> Result<()> {
// 讀取原始檔案
let mut input_file = File::open(&input_path)
.with_context(|| format!("Failed to open input file: {:?}", input_path.as_ref()))?;
let mut data = Vec::new();
input_file.read_to_end(&mut data)
.context("Failed to read input file")?;
// 生成鹽值並建立加密器
let salt = FileEncryption::generate_salt();
let encryption = FileEncryption::from_password(password, &salt)?;
// 加密資料
let encrypted_data = encryption.encrypt(&data)
.context("Failed to encrypt data")?;
// 寫入加密檔案
let mut output_file = File::create(&output_path)
.with_context(|| format!("Failed to create output file: {:?}", output_path.as_ref()))?;
// 寫入標頭(magic header + salt)
output_file.write_all(MAGIC_HEADER)
.context("Failed to write magic header")?;
output_file.write_all(&salt)
.context("Failed to write salt")?;
// 寫入加密資料
output_file.write_all(&encrypted_data)
.context("Failed to write encrypted data")?;
println!("File encrypted successfully: {:?} -> {:?}",
input_path.as_ref(), output_path.as_ref());
Ok(())
}
pub fn decrypt_file<P: AsRef<Path>>(
input_path: P,
output_path: P,
password: &str,
) -> Result<()> {
// 讀取加密檔案
let mut input_file = File::open(&input_path)
.with_context(|| format!("Failed to open encrypted file: {:?}", input_path.as_ref()))?;
let mut encrypted_content = Vec::new();
input_file.read_to_end(&mut encrypted_content)
.context("Failed to read encrypted file")?;
// 檢查檔案大小
if encrypted_content.len() < HEADER_SIZE {
anyhow::bail!("File too small to be a valid encrypted file");
}
// 檢查 magic header
if &encrypted_content[..MAGIC_HEADER.len()] != MAGIC_HEADER {
anyhow::bail!("Invalid file format or not an encrypted file");
}
// 提取鹽值
let salt = &encrypted_content[MAGIC_HEADER.len()..HEADER_SIZE];
// 提取加密資料
let encrypted_data = &encrypted_content[HEADER_SIZE..];
// 建立解密器
let encryption = FileEncryption::from_password(password, salt)?;
// 解密資料
let decrypted_data = encryption.decrypt(encrypted_data)
.context("Failed to decrypt data - wrong password or corrupted file")?;
// 寫入解密檔案
let mut output_file = File::create(&output_path)
.with_context(|| format!("Failed to create output file: {:?}", output_path.as_ref()))?;
output_file.write_all(&decrypted_data)
.context("Failed to write decrypted data")?;
println!("File decrypted successfully: {:?} -> {:?}",
input_path.as_ref(), output_path.as_ref());
Ok(())
}
// src/main.rs
mod crypto;
mod file_ops;
use clap::{Parser, Subcommand};
use anyhow::Result;
use std::io::{self, Write};
#[derive(Parser)]
#[command(name = "file-encryptor")]
#[command(about = "A secure file encryption tool using AES-256")]
#[command(version = "1.0")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Encrypt a file
Encrypt {
/// Input file path
#[arg(short, long)]
input: String,
/// Output file path
#[arg(short, long)]
output: String,
},
/// Decrypt a file
Decrypt {
/// Input encrypted file path
#[arg(short, long)]
input: String,
/// Output file path
#[arg(short, long)]
output: String,
},
}
fn get_password(prompt: &str) -> Result<String> {
print!("{}", prompt);
io::stdout().flush()?;
// 注意:在實際應用中,應該使用不會回顯密碼的輸入方式
// 這裡為了簡單示範使用標準輸入
let mut password = String::new();
io::stdin().read_line(&mut password)?;
Ok(password.trim().to_string())
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Encrypt { input, output } => {
let password = get_password("Enter password for encryption: ")?;
if password.is_empty() {
anyhow::bail!("Password cannot be empty");
}
file_ops::encrypt_file(&input, &output, &password)?;
}
Commands::Decrypt { input, output } => {
let password = get_password("Enter password for decryption: ")?;
if password.is_empty() {
anyhow::bail!("Password cannot be empty");
}
file_ops::decrypt_file(&input, &output, &password)?;
}
}
Ok(())
}
# 編譯專案
cargo build --release
# 加密檔案
./target/release/file-encryptor encrypt -i secret.txt -o secret.txt.enc
# 解密檔案
./target/release/file-encryptor decrypt -i secret.txt.enc -o decrypted.txt
打完收工~讚!